/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include "jukuframe.h"
#include "jukuapp.h"
#include "common.h"
//#include "preferencesdialog.h"
//#include "projectsettingsdialog.h"
#include "component.h"
#include "footprint.h"
#include <wx/notebook.h>
#include <wx/artprov.h>
#include <algorithm>

IMPLEMENT_DYNAMIC_CLASS(JukuFrame, wxFrame)

BEGIN_EVENT_TABLE(JukuFrame, wxFrame)
	EVT_MENU_RANGE(ID_MENU_PYTHON_COMMAND_FIRST, ID_MENU_PYTHON_COMMAND_LAST, JukuFrame::OnMenuPythonCommand)
	EVT_MENU_RANGE(ID_MENU_PYTHON_CHECK_FIRST, ID_MENU_PYTHON_CHECK_LAST, JukuFrame::OnMenuPythonCheck)
	//jukumagic menu
    //EVT_MENU(ID_MenuExit, JukuFrame::OnMenuExit)
    //EVT_MENU(ID_Preferences, JukuFrame::OnPreferences)
	//file menu
    /*EVT_MENU(ID_NewProject, JukuFrame::OnNew)
    EVT_MENU(ID_OpenProject, JukuFrame::OnOpen)
    EVT_MENU(ID_SaveProject, JukuFrame::OnSave)
    EVT_MENU(ID_SaveAsProject, JukuFrame::OnSaveAs)*/
    //EVT_MENU(ID_CloseProject, JukuFrame::OnClose)
    //EVT_MENU(ID_ProjectSettings, JukuFrame::OnProjectSettings)
	//view menu
    //EVT_MENU(ID_ShowRefdes, JukuFrame::OnShowRefdes)
    //EVT_MENU(ID_ShowValues, JukuFrame::OnShowValues)
	//toolbar2
    /*EVT_TOOL(ID_NewProject, JukuFrame::OnNew)
    EVT_TOOL(ID_OpenProject, JukuFrame::OnOpen)
    EVT_TOOL(ID_SaveProject, JukuFrame::OnSave)
    EVT_TOOL(ID_SaveAsProject, JukuFrame::OnSaveAs)
    EVT_TOOL(ID_CloseProject, JukuFrame::OnClose)
    EVT_TOOL(ID_ProjectSettings, JukuFrame::OnProjectSettings)*/
	//netlist menu
    /*EVT_MENU(ID_ImportSpiceNetlist, JukuFrame::OnImportSpiceNetlist)
    EVT_MENU(ID_ImportPartslist, JukuFrame::OnImportPartslist)
    EVT_MENU(ID_CreateRatsNest, JukuFrame::OnCreateRatsNest)
    EVT_MENU(ID_CreateInvRatsNest, JukuFrame::OnCreateInvRatsNest)
    EVT_MENU(ID_ClearRatsNest, JukuFrame::OnClearRatsNest)
	EVT_MENU(ID_ThrowNetlistComponentsOnBoard, JukuFrame::OnThrowNetlistComponentsOnBoard)
	EVT_MENU(ID_RemoveComponentsNotOnNetlist, JukuFrame::OnRemoveComponentsNotOnNetlist)*/
	//board menu
    //EVT_MENU(ID_ExportCutCSV, JukuFrame::OnExportCutCSV)
	//toolbar1
	EVT_BUTTON(ID_ZoomInButton, JukuFrame::OnZoomInButton)
	EVT_BUTTON(ID_ZoomOutButton, JukuFrame::OnZoomOutButton)

    EVT_TEXT_ENTER(ID_ZoomText, JukuFrame::OnZoomTextEnter)
    EVT_TEXT(ID_FSearchText, JukuFrame::OnFSearchText)

    EVT_TEXT_ENTER(ID_FSearchText, JukuFrame::OnFSearchTextEnter)
    EVT_COMMAND(ID_FListBox, wxEVT_FLISTBOX_NUMBER_INPUT, JukuFrame::OnFlistBoxNumberInput)
    EVT_LISTBOX(ID_FListBox, JukuFrame::OnFListBoxSelect)
    EVT_LISTBOX_DCLICK(ID_FListBox, JukuFrame::OnFListBoxDClick)

	EVT_BUTTON(ID_AddNetlCompToBoard, JukuFrame::OnAddNetlCompToBoard)
	EVT_BUTTON(ID_CheckNets, JukuFrame::OnCheckNets)
	EVT_CHECKBOX(ID_HilightSelectedNetCheck, JukuFrame::OnHilightSelectedNetCheck)

	EVT_GRID_CMD_CELL_LEFT_CLICK(ID_NetGrid, JukuFrame::OnNetGridLeftClick)
	EVT_GRID_CMD_CELL_LEFT_CLICK(ID_ComponentGrid, JukuFrame::OnComponentGridLeftClick)

    /*EVT_CHAR(JukuFrame::OnChar)
    EVT_KEY_DOWN(JukuFrame::OnKeyDown)
    EVT_KEY_UP(JukuFrame::OnKeyUp)*/
	
END_EVENT_TABLE()

JukuFrame::JukuFrame()
: wxFrame((wxFrame *)NULL, wxID_ANY, _T("jukuMagic"), wxPoint(-1,-1), wxSize(900,600))
{
	//std::cout<<"JukuFrame::JukuFrame()"<<std::endl;

	m_board = NULL;
	m_netlist = NULL;
	m_netgrid_selected_row = 0;

    wxMenuBar *menuBar = new wxMenuBar;
    SetMenuBar(menuBar);

	//board window

	wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );

    m_boardwindow = new BoardScrolledWindow((wxWindow*)this, wxID_ANY, wxDefaultPosition, wxSize(100,100));
    m_boardwindow->SetScrollRate(10, 10);
    m_boardwindow->SetFocus();

	
	wxBoxSizer *toprowsizer = new wxBoxSizer( wxHORIZONTAL );
	wxBoxSizer *middlehboxsizer = new wxBoxSizer( wxHORIZONTAL );
	wxBoxSizer *rightvboxsizer = new wxBoxSizer( wxVERTICAL );

	middlehboxsizer->Add(m_boardwindow, 5, wxEXPAND, 0);
	middlehboxsizer->Add(rightvboxsizer, 0, wxEXPAND, 0);

	topsizer->Add(toprowsizer, 0, wxEXPAND, 5);
	topsizer->Add(middlehboxsizer, 1, wxEXPAND, 5);

	SetSizer(topsizer);

	wxButton *button;
	button = new wxButton(this, ID_ZoomInButton, wxT("Zoom In"));
	toprowsizer->Add(button, 0);	
	button = new wxButton(this, ID_ZoomOutButton, wxT("Zoom Out"));
	toprowsizer->Add(button, 0);


	m_zoomtextctrl = new wxTextCtrl(this, ID_ZoomText, wxT(""),
			wxDefaultPosition, wxSize(100, -1), wxTE_PROCESS_ENTER);
	toprowsizer->Add(m_zoomtextctrl, 0);

	wxStaticText *boardmodetext = new wxStaticText(this, wxID_ANY, wxT("---"),
			wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE);
	m_boardwindow->SetModeStaticText(boardmodetext);
	toprowsizer->Add(boardmodetext, 1, wxEXPAND | wxALL, 5);

	//right notebook

	wxNotebook *rightbook = new wxNotebook(this, wxID_ANY, wxDefaultPosition,
			wxSize(wxGetApp().m_settings.m_rightbook_width, -1),
			wxNB_NOPAGETHEME | wxNB_TOP);
	
	//footprint tab

	wxPanel *footprintpanel = new wxPanel(rightbook);

	wxBoxSizer *footprint_tab_vsizer = new wxBoxSizer( wxVERTICAL );

	m_fsearchtextctrl = new wxTextCtrl(footprintpanel, ID_FSearchText, wxT(""),
			wxDefaultPosition, wxSize(100, -1), wxTE_PROCESS_ENTER);

	footprint_tab_vsizer->Add(m_fsearchtextctrl, 0, wxEXPAND, 0);


	m_footprintlist = new FListBox((wxWindow*)footprintpanel, ID_FListBox,
			wxDefaultPosition, wxDefaultSize);

	footprint_tab_vsizer->Add(m_footprintlist, 1, wxEXPAND, 0);


	footprintpanel->SetSizer(footprint_tab_vsizer);

	rightbook->AddPage(footprintpanel, wxT("Footp."));

	//board tab

	wxNotebook *boardbook = new wxNotebook(rightbook, wxID_ANY, wxDefaultPosition,
			wxSize(wxGetApp().m_settings.m_rightbook_width, 500),
			wxNB_NOPAGETHEME | wxNB_TOP);
	//

	wxPanel *componentpanel = new wxPanel(boardbook);
	wxBoxSizer *component_tab_vsizer = new wxBoxSizer( wxVERTICAL );

	m_componenttable = new ComponentTable(NULL);
	
	m_componentgrid = new wxGrid(componentpanel, ID_ComponentGrid, wxDefaultPosition,
			wxDefaultSize, 0, wxT("componentgrid"));
	m_componentgrid->SetTable(m_componenttable, true);
	m_componentgrid->SetRowLabelSize(0);
	m_componentgrid->DisableDragRowSize();
	component_tab_vsizer->Add(m_componentgrid, 1, wxEXPAND, 0);

	componentpanel->SetSizer(component_tab_vsizer);
	boardbook->AddPage(componentpanel, wxT("Components"));

	//

	rightbook->AddPage(boardbook, wxT("Board"));

	//netlist tab

	wxNotebook *netlistbook = new wxNotebook(rightbook, wxID_ANY, wxDefaultPosition,
			wxSize(wxGetApp().m_settings.m_rightbook_width, 500),
			wxNB_NOPAGETHEME | wxNB_TOP);
	//

	wxPanel *netlcomppanel = new wxPanel(netlistbook);
	wxBoxSizer *netlcomp_tab_vsizer = new wxBoxSizer( wxVERTICAL );
	
	wxBoxSizer *netlcomp_tab_hsizer1 = new wxBoxSizer( wxHORIZONTAL );
	netlcomp_tab_vsizer->Add(netlcomp_tab_hsizer1, 0, 0, 0);

	netlcomp_tab_hsizer1->Add(new wxButton(netlcomppanel, ID_AddNetlCompToBoard,
			wxT("Add selected component to board")));

	//m_netcomponenttable = new NetComponentTable(wxGetApp().m_project->m_netlist);
	m_netcomponenttable = new NetComponentTable(NULL);
	
	m_netlcompgrid = new wxGrid(netlcomppanel, ID_NetlCompGrid, wxDefaultPosition,
			wxDefaultSize, 0, wxT("netlcompgrid"));
	m_netlcompgrid->SetTable(m_netcomponenttable, true);
	m_netlcompgrid->SetRowLabelSize(0);
	m_netlcompgrid->DisableDragRowSize();
	netlcomp_tab_vsizer->Add(m_netlcompgrid, 1, wxEXPAND, 0);

	netlcomppanel->SetSizer(netlcomp_tab_vsizer);
	netlistbook->AddPage(netlcomppanel, wxT("Components"));

	//
	
	wxPanel *netpanel = new wxPanel(netlistbook);
	wxBoxSizer *net_tab_vsizer = new wxBoxSizer( wxVERTICAL );

	wxBoxSizer *net_tab_hsizer1 = new wxBoxSizer( wxHORIZONTAL );
	net_tab_vsizer->Add(net_tab_hsizer1, 0, 0, 0);

	net_tab_hsizer1->Add(new wxButton(netpanel, ID_CheckNets, wxT("Validate Nets")));

	m_hilight_selected_net_check = new wxCheckBox(netpanel, ID_HilightSelectedNetCheck,
			wxT("Hilight selected net on board"));
	net_tab_vsizer->Add(m_hilight_selected_net_check);

	//m_nettable = new NetTable(wxGetApp().m_project->m_netlist);
	m_nettable = new NetTable(NULL);
	
	m_netgrid = new wxGrid(netpanel, ID_NetGrid, wxDefaultPosition,
			wxDefaultSize, 0, wxT("netgrid"));
	m_netgrid->SetTable(m_nettable, true);
	m_netgrid->SetRowLabelSize(0);
	m_netgrid->DisableDragRowSize();
	net_tab_vsizer->Add(m_netgrid, 1, wxEXPAND, 0);

	netpanel->SetSizer(net_tab_vsizer);
	netlistbook->AddPage(netpanel, wxT("Nets"));
	
	//

	rightbook->AddPage(netlistbook, wxT("Netlist"));

	rightvboxsizer->Add(rightbook, 1, wxEXPAND, 0);


    CreateStatusBar();

    SearchFootprints(wxT(""));
}

JukuFrame::~JukuFrame()
{
	//std::cout<<"JukuFrame::~JukuFrame()"<<std::endl;
	if(m_board) m_board->RemoveChangeHandler(this);
}





void JukuFrame::OnMenuPythonCommand(wxCommandEvent &event)
{
	//std::cout<<"OnMenuPython"<<std::endl;
	//wxObject *obj = event.GetEventObject();
	//std::cout<<wxString(obj->GetClassInfo()->GetClassName()).mb_str(wxConvUTF8)<<std::endl;
	//if(!obj->IsKindOf(CLASSINFO(wxMenu))) return;
	int id = event.GetId();
	wxGetApp().m_pythonapi.ExecMenuCommand(id);
}

void JukuFrame::OnMenuPythonCheck(wxCommandEvent &event)
{
	//std::cout<<"OnMenuPython"<<std::endl;
	//wxObject *obj = event.GetEventObject();
	//std::cout<<wxString(obj->GetClassInfo()->GetClassName()).mb_str(wxConvUTF8)<<std::endl;
	//if(!obj->IsKindOf(CLASSINFO(wxMenu))) return;
	int id = event.GetId();
	int i = event.GetInt();
	wxGetApp().m_pythonapi.ExecMenuCheck(id, i);
}

void JukuFrame::RefreshBoard()
{
	// This calculates the offsets etc and refreshes the window
	m_boardwindow->OnBoardSizeOrScale();
}


//TODO: Move these two to pyjuku.cpp

void JukuFrame::OnThrowNetlistComponentsOnBoard(wxCommandEvent &event)
{
	if(!wxGetApp().m_project) return;
	if(!wxGetApp().m_project->m_board) return;

	for(std::vector<NetComponent*>::iterator i =
					wxGetApp().m_project->m_netlist.m_components.begin();
			i != wxGetApp().m_project->m_netlist.m_components.end(); i++){
		NetComponent *netc = (*i);
		netc->ThrowToBoard(wxGetApp().m_project->m_board);
	}
}

void JukuFrame::OnRemoveComponentsNotOnNetlist(wxCommandEvent &event)
{
	if(!m_board) return;
	if(!m_netlist) return;
	std::set<JukuObject*> objects_to_remove;
	for(std::list<JukuObject*>::const_iterator i = m_board->GetObjects().begin();
			i != m_board->GetObjects().end(); i++){
		if((*i)->GetType() != TYPE_COMPONENT) continue;
		Component *c = (Component*)(*i);
		bool netlist_has_it = false;
		for(std::vector<NetComponent*>::iterator i =
						m_netlist->m_components.begin();
				i != m_netlist->m_components.end(); i++){
			NetComponent *netc = (*i);
			if(netc->GetRefdes() == c->GetRefdes()){
				netlist_has_it = true;
				break;
			}
		}
		if(!netlist_has_it){
			objects_to_remove.insert(c);
		}
	}
	//m_board->SetSelected(false);
	m_board->Clear(objects_to_remove);
	m_boardwindow->Refresh();
}

//board menu

// zoom toolbar

void JukuFrame::OnZoomInButton(wxCommandEvent& event)
{
	m_boardwindow->SetScale(m_boardwindow->GetScale() * 1.5);
	UpdateZoomString();
}

void JukuFrame::OnZoomOutButton(wxCommandEvent& event)
{
	m_boardwindow->SetScale(m_boardwindow->GetScale() / 1.5);
	UpdateZoomString();
}

void JukuFrame::OnZoomTextEnter(wxCommandEvent& event)
{
	if(m_zoomtextctrl==NULL) return;
	double scale;
	wxString s = m_zoomtextctrl->GetValue();
	s.ToDouble(&scale);
	if(scale > 0.00001) m_boardwindow->SetScale(scale);
}

// footprint list

void JukuFrame::SearchFootprints(wxString s)
{
	std::list<Footprint*> list = wxGetApp().m_library.GetMatchingFootprints(s);
	m_matching_footprints.clear();
	m_matching_footprints.insert(m_matching_footprints.begin(), list.begin(), list.end());
	m_footprintlist->SetFootprints(m_matching_footprints);
}

void JukuFrame::OnFSearchText(wxCommandEvent& event)
{
	//std::cout<<"JukuFrame::OnFSearchText()"<<std::endl;
	wxString value = m_fsearchtextctrl->GetValue();
	//std::cout<<"value=\""<<value.mb_str(wxConvUTF8)<<"\""<<std::endl;
	SearchFootprints(value);
}

void JukuFrame::OnFSearchTextEnter(wxCommandEvent& event)
{
	/*
		If there is only one maching footprint, supply it.
		Otherwise, set focus to the list for selection.
	*/
	if(m_matching_footprints.size() == 1){
		m_boardwindow->SetFocus();
		m_boardwindow->SupplyFootprint(m_matching_footprints[0]);
	}
	else{
		m_footprintlist->SetFocus();
	}
}

/*
	This gets called when a number is pressed in the listbox.
*/
void JukuFrame::OnFlistBoxNumberInput(wxCommandEvent &event)
{
	//std::cout<<"JukuFrame::OnFlistBoxNumberInput()"<<std::endl;
	int number = event.GetInt();
	//std::cout<<"number="<<number<<std::endl;
	if(number >= 1 && number <= (int)m_matching_footprints.size()){
		m_boardwindow->SetFocus();
		m_boardwindow->SupplyFootprint(m_matching_footprints[number-1]);
	}
}

void JukuFrame::OnFListBoxSelect(wxCommandEvent &event)
{
}

void JukuFrame::OnFListBoxDClick(wxCommandEvent &event)
{
	Footprint *f = GetSelectedFootprint();
	m_boardwindow->SupplyFootprint(f);
}

////

void JukuFrame::OnAddNetlCompToBoard(wxCommandEvent &event)
{
	if(!m_board) return;
	if(!m_netlist) return;
	int row = m_netlcompgrid->GetGridCursorRow();
	NetComponent *netc = m_netlist->m_components[row];
	netc->ThrowToBoard(wxGetApp().m_project->m_board);
}

void JukuFrame::OnCheckNets(wxCommandEvent &event)
{
	if(!wxGetApp().m_project) return;
	if(!wxGetApp().m_project->m_board) return;
	wxGetApp().m_project->m_netlist.ValidateNets(wxGetApp().m_project->m_board);
	m_nettable->SetNetlist(&wxGetApp().m_project->m_netlist);
}

void JukuFrame::OnHilightSelectedNetCheck(wxCommandEvent &event)
{
	//std::cout<<"JukuFrame::OnHilightSelectedNetCheck()"<<std::endl;
	UpdateHilightedNet();
}

void JukuFrame::OnNetGridLeftClick(wxGridEvent &event)
{
	m_netgrid_selected_row = event.GetRow();
	UpdateHilightedNet();
	event.Skip();
}

void JukuFrame::OnComponentGridLeftClick(wxGridEvent &event)
{
	if(m_board){
		//m_boardwindow->UnselectAll();
		m_board->SetSelected(false);
		Component *c = m_componenttable->GetComponent(event.GetRow());
		//if(c != NULL) m_boardwindow->ObjectSetSelected(c, true);
		if(c != NULL) c->SetSelected(true);
	}
	event.Skip();
}

// BoardChangeHandler

void JukuFrame::ContentsChanged() // That is, board contents
{
	//std::cout<<"JukuFrame::ContentsChanged()"<<std::endl;
	UpdateHilightedNet();
	m_componenttable->SetBoard(m_board);
}

//

void JukuFrame::SetBoard(Board *board)
{
	m_board = board;
	m_componenttable->SetBoard(m_board);
	if(m_boardwindow){
		m_boardwindow->SetBoard(board);
		if(board != NULL) board->AddChangeHandler(this);
		UpdateZoomString();
	}
}

void JukuFrame::SetNetlist(Netlist *netlist)
{
	//if(DP)std::cout<<"JukuFrame::SetNetlist()"<<std::endl;

	m_netlist = netlist;

	m_netcomponenttable->SetNetlist(netlist);
	m_nettable->SetNetlist(netlist);
}

void JukuFrame::UpdateZoomString()
{
	if(m_zoomtextctrl==NULL) return;
	wxString zoomstring;
	zoomstring << m_boardwindow->GetScale();
	m_zoomtextctrl->SetValue(zoomstring);
}

void JukuFrame::FocusFootprintSearch()
{
	m_fsearchtextctrl->SetFocus();
}

void JukuFrame::FocusFootprintList()
{
	m_footprintlist->SetFocus();
}

Footprint* JukuFrame::GetSelectedFootprint()
{
	int i = m_footprintlist->GetSelection();
	if(i!=wxNOT_FOUND){
		return m_matching_footprints[i];
	}
	return NULL;
}

void JukuFrame::UpdateHilightedNet()
{
	if(m_board && m_netlist){
		m_board->m_hilighted_points.clear();
		if(m_hilight_selected_net_check->GetValue() == true){
			unsigned int row = m_netgrid_selected_row;
			if(row < m_netlist->m_nets.size()){
				m_netlist->m_nets[row]->GetPoints(m_board, m_board->m_hilighted_points);
			}
		}
		m_boardwindow->Refresh();
	}
}

void JukuFrame::UpdateFootprintList()
{
	SearchFootprints(_T(""));
}

void JukuFrame::AddAcceleratorEntry(wxAcceleratorEntry entry)
{
	m_accelerator_entries.push_back(entry);
	UpdateAcceleratorTable();
}

void JukuFrame::UpdateAcceleratorTable()
{
	//std::cerr<<"JukuFrame::UpdateAcceleratorTable(): Updating AcceleratorTable: "
	//		<<m_accelerator_entries.size()<<" entries."<<std::endl;
	wxAcceleratorTable table(m_accelerator_entries.size(), &m_accelerator_entries[0]);
	SetAcceleratorTable(table);
}

void JukuFrame::SetMouseMode(BoardWindowMouseMode newmode)
{
	if(m_boardwindow == NULL) return;
	m_boardwindow->SetMouseMode(newmode);
}


